﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.Contracts;

namespace Postnuclear.Collections.Adapters
  {
  /// <summary>
  /// Provides interface to perform observing of source collection w/o making it strongly reference the observer.
  /// </summary>
  /// <typeparam name="TSource">The type of the elements in source collection.</typeparam>
  public sealed class ObservableEnumerableWeakAdapter<TSource>
    : IObservableEnumerable<TSource>
    {
    #region Public

    // Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="ObservableEnumerableWeakAdapter{TSource}"/> class.
    /// </summary>
    /// <param name="source">The observed collection.</param>
    public ObservableEnumerableWeakAdapter(IObservableEnumerable<TSource> source)
      {
      Contract.Requires<ArgumentNullException>(source != null);

      Source = source;

      source.PropertyChanged +=
        new EventHandler<PropertyChangedEventArgs>(OnPropertyChanged).MakeWeak(
          eh => source.PropertyChanged -= eh.Invoke).Invoke;

      (source as INotifyCollectionChanged).CollectionChanged +=
        new EventHandler<NotifyCollectionChangedEventArgs>(OnCollectionChanged).MakeWeak(
          eh => (source as INotifyCollectionChanged).CollectionChanged -= eh.Invoke).Invoke;

      source.CollectionCleared +=
        new EventHandler<ValueEventArgs<IEnumerable<TSource>>>(OnCollectionCleared).MakeWeak(
          eh => source.CollectionCleared -= eh);

      source.ItemAdded +=
        new EventHandler<ObservableItemEventArgs<TSource>>(OnItemAdded).MakeWeak(eh => source.ItemAdded -= eh);

      source.ItemRemoved +=
        new EventHandler<ObservableItemEventArgs<TSource>>(OnItemRemoved).MakeWeak(eh => source.ItemRemoved -= eh);
      }

    // Events

    /// <summary>
    /// Occurs when collection is cleared.
    /// List of items that were cleared from collection is passed as arguments value.
    /// </summary>
    public event EventHandler<ValueEventArgs<IEnumerable<TSource>>> CollectionCleared;

    /// <summary>
    /// Occurs when new item is added to collection.
    /// Added item is passed as arguments value.
    /// </summary>
    public event EventHandler<ObservableItemEventArgs<TSource>> ItemAdded;

    /// <summary>
    /// Occurs when item is removed from collection.
    /// Removed item is passed as arguments value.
    /// </summary>
    public event EventHandler<ObservableItemEventArgs<TSource>> ItemRemoved;

    /// <summary>
    /// Occurs when the collection changes.
    /// </summary>
    public event NotifyCollectionChangedEventHandler CollectionChanged;

    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    // Methods

    /// <summary>
    /// Returns an enumerator that iterates through the collection.
    /// </summary>
    /// <returns>A <see cref="IEnumerator{T}"/> that can be used to iterate through the collection.</returns>
    public IEnumerator<TSource> GetEnumerator()
      {
      return Source.GetEnumerator();
      }

    /// <summary>
    /// Returns an enumerator that iterates through the collection.
    /// </summary>
    /// <returns>A <see cref="IEnumerator"/> that can be used to iterate through the collection.</returns>
    IEnumerator IEnumerable.GetEnumerator()
      {
      return GetEnumerator();
      }

    #endregion

    #region Private

    // Methods

    /// <summary>
    /// Called when <see cref="INotifyPropertyChanged.PropertyChanged"/> event is raised in the <see cref="Source"/>.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
    private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
      {
      PropertyChanged.Raise(sender, e.PropertyName);
      }

    /// <summary>
    /// Called when <see cref="INotifyCollectionChanged.CollectionChanged"/> event is raised in the <see cref="Source"/>.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
      {
      if (CollectionChanged != null)
        {
        CollectionChanged(sender, e);
        }
      }

    /// <summary>
    /// Called when <see cref="INotifyCollectionChanged{T}.CollectionCleared"/> event is raised in the <see cref="Source"/>.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="ValueEventArgs{TValue}"/> instance containing the event data.</param>
    private void OnCollectionCleared(object sender, ValueEventArgs<IEnumerable<TSource>> e)
      {
      CollectionCleared.Raise(sender, e);
      }

    /// <summary>
    /// Called when <see cref="INotifyCollectionChanged{T}.ItemAdded"/> event is raised in the <see cref="Source"/>.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="ValueEventArgs{TValue}"/> instance containing the event data.</param>
    private void OnItemAdded(object sender, ObservableItemEventArgs<TSource> e)
      {
      ItemAdded.Raise(sender, e);
      }

    /// <summary>
    /// Called when <see cref="INotifyCollectionChanged{T}.ItemRemoved"/> event is raised in the <see cref="Source"/>.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="ValueEventArgs{TValue}"/> instance containing the event data.</param>
    private void OnItemRemoved(object sender, ObservableItemEventArgs<TSource> e)
      {
      ItemRemoved.Raise(sender, e);
      }

    // Fields

    /// <summary>
    /// Gets the source collection.
    /// </summary>
    private readonly IObservableEnumerable<TSource> Source;

    #endregion
    }
  }